package com.baymax.security.filter;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baymax.security.entity.OnlineUser;
import com.baymax.security.entity.SecurityUser;
import com.baymax.security.properties.SecurityProperties;
import com.baymax.utils.DateUtil;
import com.baymax.utils.JwtUtil;
import com.baymax.utils.RedisUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 请求拦截器
 *
 * @Author: baymax
 * @CreateTime: 2023/3/30 20:50
 * @Version: 1.0
 */
@Component
@RequiredArgsConstructor
public class TokenFilter extends OncePerRequestFilter {

    private final SecurityProperties securityProperties;
    private final RedisUtil redisUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求中的token
        String tokenHeader = request.getHeader(securityProperties.getAuthentication());
        // 校验token是否合法
        if (StrUtil.isNotBlank(tokenHeader)) {
            // 请求token
            String token = removeTokenHeader(tokenHeader);
            // 在线用户key
            String onlineUserKey = JwtUtil.buildOnlineUserKey(securityProperties.getOnlineKey(), null, token);
            OnlineUser onlineUser = null;
            if (ObjectUtil.isNotNull(redisUtil.get(onlineUserKey))) {
                onlineUser = JSONObject.parseObject(redisUtil.get(onlineUserKey).toString(), OnlineUser.class);
            }
            if (ObjectUtil.isNotNull(onlineUser)) {
                // 用户认证信息
                SecurityUser securityUser = JwtUtil.buildSecurityUser(onlineUser);
                // 授权
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                // 续期token
                refreshUserToken(onlineUser, onlineUserKey, request);
            }
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 移除token前缀
     *
     * @param token 认证令牌
     */
    private String removeTokenHeader(String token) {
        return StrUtil.removePrefix(token, securityProperties.getTokenHeader());
    }

    /**
     * 续期用户token过期时间
     *
     * @param onlineUser 在线用户信息
     * @param onlineUserKey 在线用户key
     */
    private void refreshUserToken(OnlineUser onlineUser, String onlineUserKey, HttpServletRequest request) {
        // 获取请求中的refreshToken
        String refreshToken = request.getHeader(securityProperties.getRefreshAuthentication());
        if (StrUtil.isBlank(refreshToken)) {
            return;
        }
        // 比较请求中的refreshToken是否和缓存中的一致，防止抓包
        if (!refreshToken.equals(onlineUser.getRefreshToken())) {
            return;
        }
        Long expireTime = redisUtil.getExpireTime(onlineUserKey);
        // 过期时间未在1小时内
        if (expireTime > securityProperties.getRefreshTokenTime()) {
            return;
        }
        // 刷新令牌refreshToken创建时间 + 有效时间（默认30天）
        Date refreshTokenCreateDate = JwtUtil.getTokenCreateDate(removeTokenHeader(onlineUser.getRefreshToken()));
        Date effectiveDate = DateUtil.dateToLocalDateTime(refreshTokenCreateDate, securityProperties.getEffectiveDay());
        // 判断refreshToken是否有效
        if (DateUtil.now().after(effectiveDate)) {
            return;
        }
        // 更新refreshToken
        onlineUser.setRefreshToken(securityProperties.getTokenHeader() + JwtUtil.createToken(onlineUser.getSysUserVO().getUserId()));
        redisUtil.set(onlineUserKey, onlineUser, securityProperties.getExpirationTime(), TimeUnit.SECONDS);
    }

}
